﻿using Common.Exceptions;
using Common.Exceptions.MinioExceptions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Minio;
using Minio.Exceptions;
using Wallpaper.Models;
using Wallpaper.Services.IService;

namespace Wallpaper.Services.Service
{
    /// <summary>
    /// MinIO 文件存储服务实现类
    /// 提供文件上传、下载、删除、存在性检查等功能
    /// </summary>
    public class MinioService : IMinioService
    {
        private readonly MinioConfig _config;
        private readonly MinioClient _minioClient;
        private readonly ILogger<MinioService> _logger;

        /// <summary>
        /// MinIO 服务构造函数
        /// </summary>
        /// <param name="config">MinIO 配置选项</param>
        /// <param name="logger">日志记录器</param>
        /// <exception cref="ArgumentNullException">当配置为 null 时抛出</exception>
        /// <exception cref="MinioConfigurationException">当配置项缺失时抛出</exception>
        public MinioService(IOptions<MinioConfig> config, ILogger<MinioService> logger)
        {
            // 参数验证
            _config = config?.Value ?? throw new ArgumentNullException(nameof(config));
            _logger = logger;

            // 配置项完整性验证
            ValidateConfiguration();

            // 初始化 MinIO 客户端
            _minioClient = InitializeMinioClient();

            _logger.LogInformation("MinIO 服务初始化完成，Endpoint: {Endpoint}, Bucket: {Bucket}",
                _config.Endpoint, _config.BucketName);
        }

        /// <summary>
        /// 上传文件到 MinIO 存储服务
        /// </summary>
        /// <param name="objectName">对象名称（包含路径，如：images/photo.jpg）</param>
        /// <param name="fileStream">要上传的文件流</param>
        /// <param name="contentType">文件内容类型（MIME 类型）</param>
        /// <returns>上传成功返回 true，失败抛出异常</returns>
        /// <exception cref="MinioValidationException">当参数验证失败时抛出</exception>
        /// <exception cref="FileUploadException">当文件上传过程中发生错误时抛出</exception>
        public async Task<bool> UploadFileAsync(string objectName, Stream fileStream, string contentType)
        {
            // 参数前置验证
            ValidateObjectName(objectName);
            ValidateFileStream(fileStream);

            try
            {
                // 确保目标存储桶存在
                await EnsureBucketExistsAsync();

                // 构建 MinIO 文件上传参数对象
                // 使用 Fluent API 链式调用配置上传参数
                var putObjectArgs = new PutObjectArgs()
                    // 设置目标存储桶名称
                    .WithBucket(_config.BucketName)
                    // 设置对象在存储桶中的唯一标识（包含路径的文件名）
                    .WithObject(objectName)
                    // 设置要上传的文件数据流
                    .WithStreamData(fileStream)
                    // 设置文件大小（字节数），必须与实际流长度一致
                    .WithObjectSize(fileStream.Length)
                    // 设置文件内容类型（MIME 类型），用于浏览器正确识别文件类型
                    .WithContentType(contentType);

                // 执行文件上传操作到 MinIO 服务器
                await _minioClient.PutObjectAsync(putObjectArgs);

                _logger.LogInformation("文件上传成功 - 对象名称: {ObjectName}, 文件大小: {FileSize} bytes, 内容类型: {ContentType}",
                    objectName, fileStream.Length, contentType);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "文件上传失败 - 对象名称: {ObjectName}, 错误信息: {ErrorMessage}",
                    objectName, ex.Message);
                throw new FileUploadException(objectName, ex);
            }
        }

        /// <summary>
        /// 从 MinIO 存储服务下载文件
        /// </summary>
        /// <param name="objectName">要下载的对象名称</param>
        /// <returns>包含文件数据的流对象</returns>
        /// <exception cref="MinioValidationException">当参数验证失败时抛出</exception>
        /// <exception cref="StorageFileNotFoundException">当指定文件不存在时抛出</exception>
        /// <exception cref="FileDownloadException">当文件下载过程中发生错误时抛出</exception>
        public async Task<Stream> GetFileAsync(string objectName)
        {
            // 参数前置验证
            ValidateObjectName(objectName);

            try
            {
                // 检查文件是否存在，不存在则抛出异常
                if (!await FileExistsAsync(objectName))
                {
                    throw new StorageFileNotFoundException(objectName);
                }

                // 创建内存流用于存储下载的文件数据
                var memoryStream = new MemoryStream();

                // 构建下载参数
                var getObjectArgs = new GetObjectArgs()
                    .WithBucket(_config.BucketName)
                    .WithObject(objectName)
                    .WithCallbackStream(stream => stream.CopyTo(memoryStream));

                // 执行文件下载操作
                await _minioClient.GetObjectAsync(getObjectArgs);

                // 重置流位置到起始处，便于后续读取
                memoryStream.Position = 0;

                _logger.LogInformation("文件下载成功 - 对象名称: {ObjectName}, 文件大小: {FileSize} bytes",
                    objectName, memoryStream.Length);

                return memoryStream;
            }
            catch (BusinessException)
            {
                // 业务异常直接向上抛出
                throw;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "文件下载失败 - 对象名称: {ObjectName}, 错误信息: {ErrorMessage}",
                    objectName, ex.Message);
                throw new FileDownloadException(objectName, ex);
            }
        }

        /// <summary>
        /// 从 MinIO 存储服务删除指定文件
        /// </summary>
        /// <param name="objectName">要删除的对象名称</param>
        /// <returns>删除成功返回 true，失败抛出异常</returns>
        /// <exception cref="MinioValidationException">当参数验证失败时抛出</exception>
        /// <exception cref="StorageFileNotFoundException">当指定文件不存在时抛出</exception>
        /// <exception cref="FileDeleteException">当文件删除过程中发生错误时抛出</exception>
        public async Task<bool> RemoveFileAsync(string objectName)
        {
            // 参数前置验证
            ValidateObjectName(objectName);

            try
            {
                // 检查文件是否存在，不存在则抛出异常
                if (!await FileExistsAsync(objectName))
                {
                    throw new StorageFileNotFoundException(objectName);
                }

                // 构建删除参数
                var removeObjectArgs = new RemoveObjectArgs()
                    .WithBucket(_config.BucketName)
                    .WithObject(objectName);

                // 执行文件删除操作
                await _minioClient.RemoveObjectAsync(removeObjectArgs);

                _logger.LogInformation("文件删除成功 - 对象名称: {ObjectName}", objectName);
                return true;
            }
            catch (BusinessException)
            {
                // 业务异常直接向上抛出
                throw;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "文件删除失败 - 对象名称: {ObjectName}, 错误信息: {ErrorMessage}",
                    objectName, ex.Message);
                throw new FileDeleteException(objectName, ex);
            }
        }

        /// <summary>
        /// 检查指定文件在 MinIO 存储服务中是否存在
        /// </summary>
        /// <param name="objectName">要检查的对象名称</param>
        /// <returns>文件存在返回 true，不存在返回 false</returns>
        /// <exception cref="MinioValidationException">当参数验证失败时抛出</exception>
        /// <exception cref="MinioOperationException">当检查过程中发生错误时抛出</exception>
        public async Task<bool> FileExistsAsync(string objectName)
        {
            // 参数前置验证
            ValidateObjectName(objectName);

            try
            {
                // 构建文件状态检查参数
                var statObjectArgs = new StatObjectArgs()
                    .WithBucket(_config.BucketName)
                    .WithObject(objectName);

                // 执行文件存在性检查
                await _minioClient.StatObjectAsync(statObjectArgs);
                return true;
            }
            catch (ObjectNotFoundException)
            {
                // 文件不存在，返回 false
                return false;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "文件存在性检查失败 - 对象名称: {ObjectName}, 错误信息: {ErrorMessage}",
                    objectName, ex.Message);
                throw new MinioOperationException($"检查文件存在状态失败: {objectName}", ex);
            }
        }

        /// <summary>
        /// 获取文件的公开访问 URL
        /// 注意：此方法仅适用于已配置公开访问权限的存储桶
        /// </summary>
        /// <param name="objectName">对象名称</param>
        /// <returns>文件的完整访问 URL</returns>
        /// <exception cref="MinioValidationException">当参数验证失败时抛出</exception>
        public string GetFileUrl(string objectName)
        {
            // 空名称验证
            ValidateObjectName(objectName);

            // 构建并返回文件访问 URL
            var fileUrl = $"{_config.Endpoint}/{_config.BucketName}/{objectName}";
            _logger.LogDebug("生成文件访问 URL - 对象名称: {ObjectName}, URL: {FileUrl}", objectName, fileUrl);

            return fileUrl;
        }

        #region 私有方法

        /// <summary>
        /// 验证 MinIO 配置项的完整性
        /// </summary>
        /// <exception cref="MinioConfigurationException">当配置项缺失时抛出</exception>
        private void ValidateConfiguration()
        {
            if (string.IsNullOrWhiteSpace(_config.Endpoint))
                throw new MinioConfigurationException("MinIO 终端节点(Endpoint)未配置");

            if (string.IsNullOrWhiteSpace(_config.AccessKey))
                throw new MinioConfigurationException("MinIO 访问密钥(AccessKey)未配置");

            if (string.IsNullOrWhiteSpace(_config.SecretKey))
                throw new MinioConfigurationException("MinIO 秘密密钥(SecretKey)未配置");

            if (string.IsNullOrWhiteSpace(_config.BucketName))
                throw new MinioConfigurationException("MinIO 存储桶名称(BucketName)未配置");

            _logger.LogDebug("MinIO 配置验证通过");
        }

        /// <summary>
        /// 初始化并配置 MinIO 客户端
        /// </summary>
        /// <returns>配置完成的 MinIO 客户端实例</returns>
        private MinioClient InitializeMinioClient()
        {
            try
            {
                var client = new MinioClient()
                    .WithEndpoint(_config.Endpoint)           // 设置 MinIO 服务器地址
                    .WithCredentials(_config.AccessKey,       // 设置访问密钥
                                   _config.SecretKey)         // 设置秘密密钥
                    .Build();                                 // 构建客户端实例

                _logger.LogDebug("MinIO 客户端初始化成功");
                return client;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "MinIO 客户端初始化失败");
                throw new MinioConfigurationException("MinIO 客户端初始化失败，请检查配置参数");
            }
        }

        /// <summary>
        /// 确保指定的存储桶存在，如果不存在则创建
        /// </summary>
        /// <exception cref="MinioOperationException">当存储桶操作失败时抛出</exception>
        private async Task EnsureBucketExistsAsync()
        {
            try
            {
                // 检查存储桶是否存在
                var bucketExistsArgs = new BucketExistsArgs().WithBucket(_config.BucketName);
                bool bucketExists = await _minioClient.BucketExistsAsync(bucketExistsArgs);

                if (!bucketExists)
                {
                    // 存储桶不存在，创建新的存储桶
                    var makeBucketArgs = new MakeBucketArgs().WithBucket(_config.BucketName);
                    await _minioClient.MakeBucketAsync(makeBucketArgs);

                    _logger.LogInformation("MinIO 存储桶创建成功 - 存储桶名称: {BucketName}", _config.BucketName);
                }
                else
                {
                    _logger.LogDebug("MinIO 存储桶已存在 - 存储桶名称: {BucketName}", _config.BucketName);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "存储桶确保存在操作失败 - 存储桶名称: {BucketName}", _config.BucketName);
                throw new MinioOperationException($"存储桶操作失败: {_config.BucketName}", ex);
            }
        }

        /// <summary>
        /// 验证对象名称参数
        /// </summary>
        /// <param name="objectName">要验证的对象名称</param>
        /// <exception cref="MinioValidationException">当对象名称为空或空白时抛出</exception>
        private void ValidateObjectName(string objectName)
        {
            if (string.IsNullOrWhiteSpace(objectName))
            {
                throw new MinioValidationException("对象名称不能为空或空白字符串");
            }
        }

        /// <summary>
        /// 验证文件流参数
        /// </summary>
        /// <param name="fileStream">要验证的文件流</param>
        /// <exception cref="MinioValidationException">当文件流为 null 或空时抛出</exception>
        private void ValidateFileStream(Stream fileStream)
        {
            if (fileStream == null)
            {
                throw new MinioValidationException("文件流不能为 null");
            }

            if (fileStream.Length == 0)
            {
                throw new MinioValidationException("文件流不能为空（文件大小为 0）");
            }
        }

        #endregion
    }
}